package pt.compflow.interfaces.event;

import java.net.URLEncoder;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;
import org.restlet.Component;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.Protocol;
import org.restlet.data.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pt.compflow.Context;
import pt.compflow.flowontology.model.Event;
import pt.compflow.flowontology.model.FlowOntologyModel;
import pt.compflow.storage.TripleStoreConnection;
import pt.compflow.tools.OntModelHelper;

import com.hp.hpl.jena.vocabulary.RDF;

public class RestfulEventInterface extends EventInterfaceBase implements EventInterface {

	private static final Logger LOG = LoggerFactory.getLogger(RestfulEventInterface.class);
		
	private static Component component = null;
	private static int PORT = 8002;
	
	private ConcurrentHashMap<String, Restlet> restletMap = new ConcurrentHashMap<String, Restlet>();
	
	private Context context;
	
	public RestfulEventInterface() {
	}

	public RestfulEventInterface(RestfulEventInterface interfaze) {
		super(interfaze);
	}

	@Override
	public RestfulEventInterface copy() {
		return new RestfulEventInterface(this);
	}
	
	@Override
	public void start(Context context, TripleStoreConnection store) {
		if(component == null) {
			LOG.debug("Setting up component...");
			component = new Component();
			component.getServers().add(Protocol.HTTP, PORT);
			try {
				component.start();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}  
		}
	}

	@Override
	public void stop(Context context, TripleStoreConnection store) {	
		if(component.getDefaultHost().getRoutes().size() <= 0) {
			LOG.debug("No more registered Restlets. Stopping component!");
			try {
				component.stop();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			component = null;
			context = null;
		}
	}
	
	protected String setupComponent(final EventSubscription sub) throws Exception {
		
		if(restletMap.containsKey(sub.getId())) {
			LOG.warn("EventSubscription component already set up. Ignoring component set up...");
		} else {
			// Then attach it to the local host  
		    Restlet restlet = new Restlet() {
		    	@Override
		    	public void handle(Request req, Response resp) {
		    		String newEventUri = sub.getEventUri();
		    		if(newEventUri == null) {
		    			TripleStoreConnection store = context.getStoreConnection();
		    			store.openStore();
		    			FlowOntologyModel jobModel = store.connectNamedModel(sub.getJobUri());
		    			Event ev  = jobModel.createEventWithURI(context.getInstantiator().getUniqueEventId(jobModel, null, null));
		    			ev.addProperty(RDF.type, jobModel.getResource(sub.getEventClass()));
		    			newEventUri = ev.getURI();
		    			store.closeStore();
		    		}
		    		onEvent(sub.getId(), sub.getJobUri(), sub.getEventClass(), newEventUri);		
		    		resp.setStatus(Status.SUCCESS_NO_CONTENT);
		    	}
			}; 
			
		    component.getDefaultHost().attach("/event/" + URLEncoder.encode(sub.getId(), "UTF-8"), restlet);  
		    		    
		    restletMap.put(sub.getId(), restlet);
		}
	    
	    return "/event/" + URLEncoder.encode(sub.getId(), "UTF-8");
	}
	
	protected void shutdownComponent(EventSubscription sub) throws Exception {
		if(restletMap.containsKey(sub.getId())) {
			component.getDefaultHost().detach(restletMap.get(sub.getId()));
			restletMap.remove(sub.getId());
		}
	}

	@Override
	public void subscribe(Context context, TripleStoreConnection store, OntModelHelper ontModelHelper, FlowOntologyModel wfModel, FlowOntologyModel engineModel, EventSubscription sub) {
		
		if(this.context == null) this.context = context;
		
		String callbackUri = "";
		try {
			callbackUri = setupComponent(sub);
		} catch (Exception e) {
			throw new RuntimeException("Could not start event server component!", e);
		}

		pt.compflow.flowontology.model.RestfulEventInterface i = engineModel.getRestfulEventInterfaceWithURI(interfaceUri);
		String uri = i.getHasUrl(null) + "/subscribe";
		
		LOG.debug("http://localhost:" + PORT + callbackUri);
		
		RestfulRequest request = new RestfulRequest(uri, RestfulRequest.METHOD_POST);
		JSONObject obj = new JSONObject();
		try {
			obj.put("callback", callbackUri);
			obj.put("port", PORT);
			request.setBody(obj.toString());
		} catch (Exception e) {
			try {
				shutdownComponent(sub);
			} catch (Exception ex) {
				LOG.error("Error shutting down RestfulEventInterface " + interfaceUri + " for Event " + sub.getEventUri(), ex);
			}
			throw new RuntimeException("Error building event subscription request.", e);
		}
		
		if(!processRequest(request)) {
			try {
				shutdownComponent(sub);
			} catch (Exception e) {
				LOG.error("Error shutting down RestfulEventInterface " + interfaceUri + " for Event " + sub.getEventUri(), e);
			}
			throw new RuntimeException("Could not subscribe Event " + sub.getEventUri() + " to Event Interface" + interfaceUri);
		}

	}

	@Override
	public void unsubscribe(Context context, TripleStoreConnection store, OntModelHelper ontModelHelper, FlowOntologyModel wfModel, FlowOntologyModel engineModel, EventSubscription sub) {
		
		pt.compflow.flowontology.model.RestfulEventInterface i = engineModel.getRestfulEventInterfaceWithURI(interfaceUri);
		String uri = i.getHasUrl(null) + "/unsubscribe";
		
		RestfulRequest request = new RestfulRequest(uri, RestfulRequest.METHOD_POST);
//		JSONObject obj = new JSONObject();
//		try {
//			obj.put("u", username);
//			obj.put("p", passwordMd5);
//			obj.put("v", VERSION);
//			request.setBody(obj.toString());
//		} catch (Exception e) {
//			LOG.error("Error building register user request for server.", e);
//			throw new RuntimeException("Error building register user request for server.", e);
//		}
		
		processRequest(request);
		
		try {
			shutdownComponent(sub);
		} catch (Exception e) {
			throw new RuntimeException("Could not stop event server component!", e);
		}
	}

	private boolean processRequest(RestfulRequest request) {
		
		int responseCode = 0;
		int it = 0;
		
		do {
			if(it > 0) {
				LOG.debug("Could not connect to event interface " + request.getEndpoint() + ". HTTP Code = " + responseCode + ". Retry " + it);
				try {
					// wait 30 seconds between iterations
					Thread.sleep(30000);
				} catch (InterruptedException e) {	}	
			}
		
			try {

				HttpUriRequest httpRequest = request.buildRequest();
	
				if (httpRequest != null) {
					HttpClient client = new DefaultHttpClient();
				
					HttpResponse httpResponse = client.execute(httpRequest);
					responseCode = httpResponse.getStatusLine().getStatusCode();
					
//					if(responseCode >= 200 && responseCode < 300) {
//				
//						HttpEntity entity = httpResponse.getEntity();
//		
//						if (entity != null) {
//		
//							InputStream instream = entity.getContent();
//							BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
//							StringBuilder sb = new StringBuilder();
//							String line = reader.readLine();
//							
//							
//							while(line != null) {
//								sb.append(line);
//								line = reader.readLine();
//							}
//		
//							instream.close();
//						}
//					}
					
					
				} else
					LOG.error("Restful request is null. Probably an invalid method. Event not subscribed!");
	
			} catch (Throwable e) {
				LOG.error("Error while performing Restful request. Event not subscribed!", e);
			}
			
			it++;
			
		} while(it < 3 && (responseCode < 200 || responseCode >= 300));
		
		return responseCode >= 200 && responseCode < 300;
	}

}
